package com.hqd.ch03.v41.web.servlet;


import com.hqd.ch03.v41.context.stereotype.Controller;
import com.hqd.ch03.v41.utils.NestedServletException;
import com.hqd.ch03.v41.web.context.request.ServletWebRequest;
import com.hqd.ch03.v41.web.http.HttpMethod;
import com.hqd.ch03.v41.web.http.server.ServletServerHttpRequest;
import com.hqd.ch03.v41.web.multipart.MultipartHttpServletRequest;
import com.hqd.ch03.v41.web.multipart.MultipartResolver;
import com.hqd.ch03.v41.web.multipart.support.StandardServletMultipartResolver;
import com.hqd.ch03.v41.web.servlet.handler.RequestMappingHandlerMapping;
import com.hqd.ch03.v41.web.servlet.handler.SimpleServletHandlerAdapter;
import com.hqd.ch03.v41.web.servlet.handler.SimpleUrlHandlerMapping;
import com.hqd.ch03.v41.web.servlet.i18n.SessionLocaleResolver;
import com.hqd.ch03.v41.web.servlet.mvc.ServletWrappingController;
import com.hqd.ch03.v41.web.servlet.mvc.SimpleControllerHandlerAdapter;
import com.hqd.ch03.v41.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import com.hqd.ch03.v41.web.servlet.support.DefaultHandlerExceptionResolver;
import com.hqd.ch03.v41.web.servlet.support.SessionFlashMapManager;
import com.hqd.ch03.v41.web.servlet.theme.SessionThemeResolver;
import com.hqd.ch03.v41.web.servlet.view.InternalResourceViewResolver;
import com.hqd.ch03.v41.web.utils.WebUtils;
import org.reflections.Reflections;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

public class DispatcherServlet extends HttpServlet {
    public static final String EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + ".EXCEPTION";
    public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
    public static final String INPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP";
    public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
    public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP";
    public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
    public static final String FLASH_MAP_MANAGER_ATTRIBUTE = DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER";

    private static final String DEFAULT_STRATEGIES_PREFIX = "org.springframework.web.servlet";
    private static final String PREFIX = "/views/";
    private static final String SUFFIX = ".jsp";
    private static final String BASE_PACKAGE = "com.hqd.ch03.test.web.v41";
    private List<HandlerMapping> handlerMappings = new ArrayList<>();
    private List<HandlerAdapter> handlerAdapters = new ArrayList<>();
    private List<ViewResolver> viewResolvers;
    private MultipartResolver multipartResolver;
    private List<HandlerExceptionResolver> handlerExceptionResolvers = new ArrayList<>();
    private boolean throwExceptionIfNoHandlerFound = false;
    private boolean cleanupAfterInclude = true;
    private LocaleResolver localeResolver;
    private ThemeResolver themeResolver;
    private FlashMapManager flashMapManager;

    private static String getRequestUri(HttpServletRequest request) {
        String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
        if (uri == null) {
            uri = request.getRequestURI();
        }
        return uri;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        initStrategies();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try {
            doService(req, resp);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        try {
            doService(req, resp);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            //清理include
            attributesSnapshot = new HashMap<>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
        //设置国际化处理器
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        //设置主题处理器
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        /**
         * 存在闪存管理器
         */
        if (this.flashMapManager != null) {
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                /**
                 * 已存在，转成不可修改的
                 */
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }
            //不存在，建立新的缓存
            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
        }
        doDispatch(request, response);
    }

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        try {
            Exception dispatchException = null;
            ModelAndView mv = null;
            try {
                request = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    /**
                     * 没找到对应的控制器
                     */
                    noHandlerFound(processedRequest, response);
                    return;
                }
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                String method = processedRequest.getMethod();
                boolean isGet = HttpMethod.GET.matches(method);
                if (isGet || HttpMethod.HEAD.matches(method)) {
                    long lastModified = ha.getLastModified(processedRequest, mappedHandler.getHandler());
                    /**
                     * 资源没改变，直接返回
                     */
                    if (new ServletWebRequest(processedRequest, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                mappedHandler.applyPreHandle(processedRequest, response);
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception ex) {
                dispatchException = ex;
            } catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        } catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        } finally {
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                       HandlerExecutionChain mappedHandler, ModelAndView mv,
                                       Exception exception) throws Exception {
        boolean errorView = false;
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            } else {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }
        if (mv != null && !mv.wasCleared()) {
            /**
             * 渲染
             */
            render(mv, request, response);
            if (errorView) {//存在视图，则清除request上的信息（有model）
                WebUtils.clearErrorRequestAttributes(request);
            }
        }

        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }

    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
                                                   Object handler, Exception ex) throws Exception {
        request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        ModelAndView exMv = null;
        if (this.handlerExceptionResolvers != null) {
            /**
             * 查找对应的异常处理器
             */
            for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
                exMv = resolver.resolveException(request, response, handler, ex);
                if (exMv != null) {
                    break;
                }
            }
        }
        if (exMv != null) {
            /**
             *
             * model不为空，则添加异常信息
             */
            if (exMv.isEmpty()) {
                request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
                return null;
            }
            /**
             * 暴露出Servlet规范的异常信息
             */
            WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
            return exMv;
        }
        throw ex;
    }


    private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
                                        HandlerExecutionChain mappedHandler, Exception ex) throws Exception {

        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, ex);
        }
        throw ex;
    }

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        View view;
        String viewName = mv.getViewName();
        if (viewName != null) {
            view = resolveViewName(viewName, mv.getModelInternal(), null, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                        "' in servlet with name '" + getServletName() + "'");
            }
        } else {
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                        "View object in servlet with name '" + getServletName() + "'");
            }
        }
        try {
            if (mv.getStatus() != null) {
                request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
                response.setStatus(mv.getStatus().value());
            }
            view.render(mv.getModelInternal(), request, response);
        } catch (Exception ex) {
            throw ex;
        }
    }

    protected void cleanupMultipart(HttpServletRequest request) {
        if (this.multipartResolver != null) {
            MultipartHttpServletRequest multipartRequest =
                    WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
            if (multipartRequest != null) {
                this.multipartResolver.cleanupMultipart(multipartRequest);
            }
        }
    }

    protected View resolveViewName(String viewName, Map<String, Object> model,
                                   Locale locale, HttpServletRequest request) throws Exception {
        if (this.viewResolvers != null) {
            /**
             * 使用事务定位器进行定位
             */
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }

    protected HttpServletRequest checkMultipart(HttpServletRequest request) {
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
            if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
                try {
                    return this.multipartResolver.resolveMultipart(request);
                } catch (RuntimeException ex) {
                    if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                        ex.printStackTrace();
                    } else {
                        throw ex;
                    }
                }
            }
        }
        return request;
    }

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            for (HandlerAdapter adapter : this.handlerAdapters) {
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }
        throw new ServletException("No adapter for handler [" + handler +
                "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }

    protected void initStrategies() {
        /**
         *1.初始化文件上传
         */
        initMultipartResolver();
        /**
         * 2.初始化国际化
         */
        initLocaleResolver();
        /**
         * 3.初始化主题
         */
        initThemeResolver();
        /**
         * 4.初始化url处理器
         */
        initHandlerMappings();
        /**
         * 5.初始化适配器
         */
        initHandlerAdapters();
        /**
         * 6.初始化异常处理
         */
        initHandlerExceptionResolvers();
        /**
         * 初始化视图
         */
        initViewResolvers();
        /**
         * 初始化闪存管理器
         */
        initFlashMapManager();
    }

    private void initFlashMapManager() {
        this.flashMapManager = new SessionFlashMapManager();
    }

    private void initThemeResolver() {
        this.themeResolver = new SessionThemeResolver();
    }

    private void initLocaleResolver() {
        this.localeResolver = new SessionLocaleResolver();
    }

    private void initHandlerExceptionResolvers() {
        this.handlerExceptionResolvers.add(new DefaultHandlerExceptionResolver());
    }

    private void initMultipartResolver() {
        this.multipartResolver = new StandardServletMultipartResolver();
    }

    private void initViewResolvers() {
        this.viewResolvers = new ArrayList<>();
        this.viewResolvers.add(new InternalResourceViewResolver(PREFIX, SUFFIX));
    }

    private void initHandlerAdapters() {
        this.handlerAdapters.add(new SimpleControllerHandlerAdapter());
        this.handlerAdapters.add(new SimpleServletHandlerAdapter());
        RequestMappingHandlerAdapter rmha = new RequestMappingHandlerAdapter();
        try {
            rmha.afterPropertiesSet();
        } catch (Exception e) {
            throw new RuntimeException("初始化异常:", e);
        }
        this.handlerAdapters.add(rmha);
    }

    private void initHandlerMappings() {
        RequestMappingHandlerMapping rmhm = new RequestMappingHandlerMapping();
        SimpleUrlHandlerMapping shm = new SimpleUrlHandlerMapping();
        Reflections reflections = new Reflections(BASE_PACKAGE);
        // 获取指定包下所有类
        Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Controller.class);
        Set<Class<? extends HandlerInterceptor>> interceptors = reflections.getSubTypesOf(HandlerInterceptor.class);
        Set<Class<? extends HttpServlet>> cs = reflections.getSubTypesOf(HttpServlet.class);
        for (Class<? extends Servlet> clazz : cs) {
            try {
                ServletWrappingController swc = new ServletWrappingController(clazz);
                swc.afterPropertiesSet();
                //这里默认使用类名，实际上应该由用户配置
                shm.registerHandler("/" + clazz.getSimpleName(), swc);
            } catch (Exception e) {
                throw new RuntimeException("class [" + clazz.getName() + "] create is error，because：" + e.getMessage());
            }
        }
        /**
         * 注册控制器
         */
        for (Class<?> clazz : classes) {
            try {
                rmhm.init(clazz.newInstance());
            } catch (Exception e) {
                throw new RuntimeException("class [" + clazz.getName() + "] create is error，because：" + e.getMessage());
            }
        }
        /**
         * 注册拦截器
         */
        for (Class<? extends HandlerInterceptor> clazz : interceptors) {
            try {
                rmhm.setInterceptors(clazz.newInstance());
            } catch (Exception e) {
                throw new RuntimeException("class [" + clazz.getName() + "] create is error，because：" + e.getMessage());
            }
        }
        rmhm.initInterceptors();

        this.handlerMappings.add(rmhm);
        this.handlerMappings.add(shm);
    }

    public boolean isThrowExceptionIfNoHandlerFound() {
        return throwExceptionIfNoHandlerFound;
    }

    public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) {
        this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound;
    }

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping mapping : this.handlerMappings) {
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

    protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (this.throwExceptionIfNoHandlerFound) {
            throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
                    new ServletServerHttpRequest(request).getHeaders());
        } else {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }

    public List<HandlerExceptionResolver> getHandlerExceptionResolvers() {
        return handlerExceptionResolvers;
    }
}
